package com.xuecheng.config;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

/**
 * 网关认证过虑器
 */
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {

    // 用于管理OAUth2认证令牌的存储和访问
    @Autowired
    private TokenStore tokenStore;

    /**
     * getOrder()方法是实现了 Ordered接口 的方法，用于指定过滤器的执行顺序
     *
     * @return 0：表明该过滤器的执行顺序为最高优先级
     */
    @Override
    public int getOrder() {
        return 0;
    }

    // 白名单
    private static List<String> whitelist = null;

    // 加载白名单（服务启动时加载，且只加载一次）
    static {
        try (InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties")) {
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            Set<String> strings = properties.stringPropertyNames();
            whitelist = new ArrayList<>(strings);
        } catch (Exception e) {
            log.error("加载/security-whitelist.properties出错:{}", e.getMessage());
        }
    }


    /**
     * 获取Token
     *
     * @param exchange 服务器与客户端之间的HTTP请求对象
     * @return Token
     */
    private String getToken(ServerWebExchange exchange) {
        // 从请求头中获取Token（带Bearer）
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        // Token信息为空
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        // 获取Token（期望令牌是Bearer令牌，因此从令牌字符串中删除Bearer前缀）
        String token = tokenStr.split(" ")[1];
        // Token为空
        if (StringUtils.isBlank(token)) {
            return null;
        }
        // 返回Token
        return token;
    }


    /**
     * 检查请求的URL是否在白名单中
     *
     * @param exchange 服务器与客户端之间的HTTP请求对象
     * @param chain    过滤器链
     * @return 异步编程操作的完成状态，该操作不返回任何值
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求的URL
        String requestUrl = exchange.getRequest().getPath().value();
        // 路径匹配器
        AntPathMatcher pathMatcher = new AntPathMatcher();
        // 白名单放行
        for (String url : whitelist) {
            if (pathMatcher.match(url, requestUrl)) {
                // 放行
                return chain.filter(exchange);
            }
        }

        // 获取Token
        String token = getToken(exchange);
        // Token为空
        if (StringUtils.isBlank(token)) {
            return buildReturnMono("没有认证", exchange);
        }
        // 判断Token是否有效
        OAuth2AccessToken oAuth2AccessToken;
        try {
            // 从tokenStore中读取正在访问的Token
            oAuth2AccessToken = tokenStore.readAccessToken(token);
            // 判断Token是否已过期
            boolean expired = oAuth2AccessToken.isExpired();
            if (expired) {
                return buildReturnMono("认证令牌已过期", exchange);
            }
            // 未过期，进行放行
            return chain.filter(exchange);
        } catch (InvalidTokenException e) {
            log.info("认证令牌无效: {}", token);
            return buildReturnMono("认证令牌无效", exchange);
        }

    }


    /**
     * 构建错误响应
     *
     * @param error    错误信息
     * @param exchange 服务器与客户端之间的HTTP请求对象
     * @return JSON格式的错误消息
     */
    private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
        // 创建响应对象
        ServerHttpResponse response = exchange.getResponse();
        // 设置错误信息
        String jsonString = JSON.toJSONString(new RestErrorResponse(error));
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        // 设置错误状态码
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        // 设置响应头
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        // 返回响应对象（该响应将包含一个JSON格式的错误消息）
        return response.writeWith(Mono.just(buffer));
    }

}
